﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows.Data;
using System.Windows.Input;
using BaMusaSoft.RPS.DataModel;
using BaMusaSoft.RPS.DataModel.Repositories;
using BaMusaSoft.RPS.RPSServices;
using BaMusaSoft.RPS.ViewModel.HelperClasses;
using BaMusaSoft.RPS.ViewModel.Infrastructure;
using BaMusaSoft.RPS.ViewModel.Properties;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;

namespace BaMusaSoft.RPS.ViewModel
{
    public class ScheduleViewModel : Workspace, IDataErrorInfo
    {
        #region "Fields"

        private readonly Dictionary<string, string> _errors = new Dictionary<string, string>();
        private readonly RepositoryBase<Schedule> _repository;
        private readonly Settings _settings;
        private readonly IUnitOfWork _unitOfWork;
        private RelayCommand _createNewCommand;
        private RelayCommand _deleteCommand;
        private RelayCommand _deleteDetailCommand;
        private ObservableCollection<ScheduleDetail> _details;
        private ScheduleDetailsStatistics _detailsStatistics;
        private string _displayName;
        private bool _isNewSchedule;
        private Logger _logger;
        private RelayCommand _printCommand;
        private RelayCommand _refreshCommand;
        private RelayCommand _saveCommand;
        private Schedule _schedule;
        private RelayCommand _searchCommand;
        private string _searchCriteria;
        private Schedule _searchResultSchedule;
        private SearchSchedule _searchSchedule;
        private ScheduleDetail _selectedDetail;
        private bool _openedForEdit;
        #endregion

        #region "Constructor"

        public ScheduleViewModel(IUnitOfWork unitOfWork, Schedule schedule)
        {
            if (unitOfWork == null) throw new ArgumentNullException("unitOfWork");
            if (schedule == null) throw new ArgumentNullException("schedule");
            _schedule = schedule;
            _unitOfWork = unitOfWork;
            _repository = _unitOfWork.Schedules;
            _settings = Settings.Default;
            if (!string.IsNullOrEmpty(schedule.ScheduleId))
            {
                OnStateChanged(ViewModelState.Loaded);
                _openedForEdit = true;

            }
            else
            {
                OnStateChanged(ViewModelState.New);
                _openedForEdit = false;
            }
            //SchedulePrinterHelper.Service.JobCompleted += OnSchedulePrinterCompleted;
        }



        #endregion

        #region "Binding Properties"

        #region ScheduleProperties

        [Bindable(true)]
        public string ScheduleId
        {
            get { return _schedule.ScheduleId; }
            private set
            {
                if (value != _schedule.ScheduleId)
                {
                    _schedule.ScheduleId = value;
                    RaisePropertyChanged("ScheduleId");
                }
            }
        }

        [Bindable(true)]
        public string RegistredAt
        {
            get { return _schedule.RegistredAt; }
            set
            {
                if (value != _schedule.RegistredAt)
                {
                    _schedule.RegistredAt = value;
                    RaisePropertyChanged("RegistredAt");
                }
            }
        }

        [Bindable(true)]
        public string Remarks
        {
            get { return _schedule.Remarks; }
            set
            {
                if (value != _schedule.Remarks)
                {
                    _schedule.Remarks = value;
                    RaisePropertyChanged("Remarks");
                }
            }
        }

        #endregion

        #region "Contract Properties"

        [Bindable(true)]
        public string PropertyNo
        {
            get
            {
                if (_schedule.Contract != null)
                {
                    return _schedule.Contract.Property.PropertyNo;
                }
                return null;
            }
        }

        [Bindable(true)]
        public string CustomerName
        {
            get
            {
                if (_schedule.Contract != null && _schedule.Contract.Customer != null)
                {
                    return _schedule.Contract.Customer.Name;
                }
                return null;
            }
        }

        [Bindable(true)]
        public ReadOnlyObservableCollection<Contract> ContractWrapper
        {
            get
            {
                var contracts = new ObservableCollection<Contract>();
                if (_schedule.Contract != null)
                {
                    contracts.Add(_schedule.Contract);
                }
                return new ReadOnlyObservableCollection<Contract>(contracts);
            }
        }

        #endregion

        #region "Signer Properties"

        [Bindable(true)]
        public string SignerId
        {
            get
            {
                if (_schedule.Signer != null)
                {
                    return _schedule.Signer.IdNumber;
                }
                return null;
            }
        }

        [Bindable(true)]
        public string SignerName
        {
            get
            {
                if (_schedule.Signer != null)
                {
                    return _schedule.Signer.Name;
                }
                return null;
            }
        }

        [Bindable(true)]
        public string SignerMobile
        {
            get
            {
                if (_schedule.Signer != null)
                {
                    return _schedule.Signer.Mobile;
                }
                return null;
            }
        }

        [Bindable(true)]
        public string SignerPhone
        {
            get
            {
                if (_schedule.Signer != null)
                {
                    return _schedule.Signer.FirstPhone;
                }
                return null;
            }
        }

        #endregion

        #region "Details Properties"

        [Bindable(true)]
        public ObservableCollection<ScheduleDetail> Details
        {
            get
            {
                if (_openedForEdit)
                {
                    #region "UICollection Stuff"
                    //This operation is necessary due to chance of the UI collection lefted in Editing or AddingNew mode,
                    //Which will cause an InvalidOperationException thrown with message: 
                    //"'DeferRefresh' is not allowed during an AddNew or EditItem transaction.".
                    //This can repreduced with follow these steps:
                    //1- Comment What inside this region.
                    //2- Start debuggin and open ALLSchedulesView.
                    //3- From there select a schedule and opent for edit.
                    //4- When schedule opened for edit, start editing any ScheduleDetails Grid cells.
                    //5- Without hitting enter twice -which will force to end editing transaction- switch to
                    //AllScheduleView, then go back to the edited schedule, at that moment the exception above will be thrown.
                    var uiCollection = CollectionViewSource.GetDefaultView(_details) as IEditableCollectionView;
                    if (uiCollection != null)
                    {
                        //Console.WriteLine("UICollection Not Null");
                        if (uiCollection.IsEditingItem)
                        {
                            uiCollection.CommitEdit();
                            //Console.WriteLine("UICollection Commit Edit Changes");
                        }
                        if (uiCollection.IsAddingNew)
                        {

                            uiCollection.CommitNew();
                            //Console.WriteLine("UICollection Commit Adding New");
                        }
                    }
                    #endregion
                }

                return _details;
            }
            private set
            {
                _details = value;
                RaisePropertyChanged("Details");
            }
        }

        public ScheduleDetailsStatistics Statistics
        {
            get { return _detailsStatistics; }
            private set
            {
                _detailsStatistics = value;
                RaisePropertyChanged("Statistics");
            }
        }

        #endregion

        #endregion

        #region "Public Properties"

        public IUnitOfWork UnitOfWork
        {
            get { return _unitOfWork; }
        }

        #endregion

        #region "Commands"

        public ICommand SaveCommand
        {
            get { return _saveCommand ?? (_saveCommand = new RelayCommand(Save)); }
        }

        public ICommand PrintCommand
        {
            get { return _printCommand ?? (_printCommand = new RelayCommand(Print)); }
        }

        public bool CanPrint
        {
            get { return true; }
        }

        public ICommand DeleteDetailCommand
        {
            get { return _deleteDetailCommand ?? (_deleteDetailCommand = new RelayCommand(DeleteDetail, CanDeleteDetail)); }
        }

        public ICommand CreateNewCommand
        {
            get { return _createNewCommand ?? (_createNewCommand = new RelayCommand(CreateNew)); }
        }

        public ICommand DeleteCommand
        {
            get { return _deleteCommand ?? (_deleteCommand = new RelayCommand(DeleteSchedule, CanDeleteSchedule)); }
        }

        public ICommand RefreshCommand
        {
            get { return _refreshCommand ?? (_refreshCommand = new RelayCommand(RefreshContext)); }
        }

        private void Save()
        {
            if (!ValidModel()) return;
            try
            {
                if (_isNewSchedule)
                {
                    if (((SchedulesRepository)_repository).IsScheduledContract(_schedule.Contract))
                    {
                        string msg = Resources.ContractHadScheduledMsg;
                        Messenger.Default.Send(msg, MessageToken);
                        return;
                    }
                    ScheduleId = GenerateScheduleId().ToString();
                    _repository.Add(_schedule);
                }
                _unitOfWork.Save();
                OnStateChanged(ViewModelState.Saved);
            }
            catch (Exception ex)
            {
                Messenger.Default.Send(ex.Message, MessageToken);
            }
        }


        private void Print()
        {
            Schedule printable = _schedule.DeepClone();
            try
            {
                SchedulePrinterHelper.Print(printable);
            }
            catch (Exception ex)
            {
                string msg = ex.Message;
                Messenger.Default.Send(msg, MessageToken);
            }
        }

        private void DeleteDetail()
        {
            string msg = Resources.DeleteDetailConfMsg;
            if (Helper.MessageConfirmed(msg, MessageToken))
                Details.Remove(SelectedDetail);
        }

        private bool CanDeleteDetail()
        {
            return
                (Details.Count > 0 &&
                 SelectedDetail != null &&
                 Details.Contains(SelectedDetail));
        }

        private void CreateNew()
        {
            if (HasChanges)
            {
                string msg = Resources.PrompetToSaveMsg;
                if (!Helper.MessageConfirmed(msg, MessageToken)) return;
                OnStateChanged(ViewModelState.New);
                RefreshBinding();
                return;
            }
            OnStateChanged(ViewModelState.New);
            RefreshBinding();

        }

        private void DeleteSchedule()
        {
            string msg = Resources.DeleteScheduleConfMsg;
            if (Helper.MessageConfirmed(msg, MessageToken))
            {
                try
                {
                    _repository.Delete(_schedule);
                    _unitOfWork.Save();
                    OnStateChanged(ViewModelState.Deleted);
                    RefreshBinding();
                }
                catch (Exception ex)
                {
                    string exMessage = Helper.GetAllExceptionMessages(ex);
                    Messenger.Default.Send(exMessage, MessageToken);
                }
            }
        }
        private bool CanDeleteSchedule()
        {
            return !_isNewSchedule;
        }
        private void RefreshContext()
        {
        }

        #endregion

        #region "Helpers"

        public ScheduleDetail SelectedDetail
        {
            get { return _selectedDetail; }
            set
            {
                _selectedDetail = value;
                RaisePropertyChanged("SelectedDetail");
            }
        }


        private void RefreshBinding()
        {
            IEnumerable<PropertyInfo> bindableProperties = from p in typeof(ScheduleViewModel).GetProperties()
                                                           where
                                                               p.GetCustomAttributes(true).Cast<Attribute>().Contains(
                                                                   new BindableAttribute(true))
                                                           select p;

            foreach (PropertyInfo p in bindableProperties)
            {
                RaisePropertyChanged(p.Name);
            }
        }

        private void SyncDetails(IEnumerable<ScheduleDetail> details, ViewModelState state)
        {
            if (Details != null && state != ViewModelState.Saved)
                Details.CollectionChanged -= OnDetailsChanged;

            /* Follow these steps:
             * schedule new 
             * Clear details.
             * Create new details.
             * 
             * schedule saved
             * don't do anything as Details in sync with _schedule.Details.
             * 
             * schedule deleted
             * Clear details.
             * Create new details.
             * 
             * schedule loaded
             * Sync _schedule.Details to Details.
             * Subscribe to Detail PropertyChanged event.
             */
            switch (state)
            {
                case ViewModelState.New:
                    ClearDetails();
                    Details = new ObservableCollection<ScheduleDetail>(details);
                    Details.CollectionChanged += OnDetailsChanged;
                    break;
                case ViewModelState.Saved:
                    //Do nothing.
                    break;
                case ViewModelState.Deleted:
                    ClearDetails();
                    Details = new ObservableCollection<ScheduleDetail>(details);
                    Details.CollectionChanged += OnDetailsChanged;
                    break;
                case ViewModelState.Loaded:
                    Details = new ObservableCollection<ScheduleDetail>(details);
                    Details.CollectionChanged += OnDetailsChanged;
                    if (Details.Count != 0)
                    {
                        foreach (ScheduleDetail detail in Details)
                        {
                            detail.PropertyChanged += OnDetailChanged;
                        }
                    }
                    break;
                default:
                    throw new ArgumentOutOfRangeException("state");
            }
            UpdateDetailsStatistics();
        }

        private void OnDetailsChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null && e.NewItems.Count != 0)
            {
                foreach (ScheduleDetail detail in e.NewItems)
                {
                    //Get notified for any changes.
                    detail.PropertyChanged += OnDetailChanged;
                    _schedule.ScheduleDetails.Add(detail);
                }
            }
            if (e.OldItems != null && e.OldItems.Count != 0)
            {
                foreach (ScheduleDetail detail in e.OldItems)
                {
                    //Remove notification event handler.
                    detail.PropertyChanged -= OnDetailChanged;
                    _schedule.ScheduleDetails.Remove(detail);
                }
            }
            UpdateDetailsStatistics();
        }

        private void OnDetailChanged(object sender, PropertyChangedEventArgs e)
        {
            UpdateDetailsStatistics();
        }

        private void UpdateDetailsStatistics()
        {
            int dueSum = Details.Sum(x => x.AmountDue);
            int paidSum = Details.Sum(x => x.AmountPaid);
            Statistics = new ScheduleDetailsStatistics(dueSum, paidSum);
        }

        private void OnStateChanged(ViewModelState state)
        {
            switch (state)
            {
                case ViewModelState.New:
                    _schedule = new Schedule();
                    _isNewSchedule = true;
                    DisplayName = Resources.NewDispalyName;
                    break;
                case ViewModelState.Saved:
                    _isNewSchedule = false;
                    DisplayName = _schedule.ScheduleId;
                    break;
                case ViewModelState.Loaded:
                    _isNewSchedule = false;
                    DisplayName = _schedule.ScheduleId;
                    break;
                case ViewModelState.Deleted:
                    _schedule = new Schedule();
                    _isNewSchedule = true;
                    DisplayName = Resources.NewDispalyName;
                    break;
                default:
                    throw new ArgumentOutOfRangeException("state");
            }
            SyncDetails(_schedule.ScheduleDetails, state);
            Messenger.Default.Send(state, MessageToken);
        }

        private bool ValidModel()
        {
            ClearAllErrors();
            bool valid = true;
            if (string.IsNullOrEmpty(RegistredAt) || !Helper.IsValidDate(RegistredAt))
            {
                string msg = Resources.SchRegiErrMsg;
                valid = false;
                SetError("RegistredAt", msg);
            }
            if (string.IsNullOrEmpty(CustomerName))
            {
                string msg = Resources.SchContErrMsg;
                valid = false;
                SetError("CustomerName", msg);
                SetError("PropertyNo", msg);
            }
            if (string.IsNullOrEmpty(SignerId))
            {
                string msg = Resources.SchSigErrMsg;
                valid = false;
                SetError("SignerId", msg);
                SetError("SignerName", msg);
                SetError("SignerMobile", msg);
                SetError("SignerPhone", msg);
            }
            if (Details.Count == 0)
            {
                string msg = Resources.SchDetialsErrMsg;
                valid = false;
                SetError("Details", msg);
            }
            return valid;
        }

        private int GenerateScheduleId()
        {
            string currentYear = _settings.CurrentYear;
            if (string.IsNullOrEmpty(currentYear)) throw new InvalidOperationException(Resources.CurrentYearMissingMsg);
            if (!Helper.YearInRange(currentYear)) throw new InvalidOperationException(Resources.MsgCurrentYearNotInRange);
            string max = ((SchedulesRepository)_repository).MaxSchedule;
            if (!string.IsNullOrEmpty(max))
            {
                string yearPosi = max.Substring(0, 4);
                if (yearPosi == currentYear)
                {
                    int incrementedNo;
                    if (int.TryParse(max, out incrementedNo))
                    {
                        incrementedNo++;
                    }
                    return incrementedNo;
                }
            }
            return Helper.StartNewIncrement(currentYear);
        }
        private void ClearDetails()
        {
            if (Details == null) return;
            foreach (var scheduleDetail in Details)
            {
                scheduleDetail.PropertyChanged -= OnDetailChanged;
            }
            Details.Clear();
        }
        private void OnSchedulePrinterCompleted(object sender, ServiceCompletedEventArgs e)
        {
            switch (e.Result)
            {
                case CompletionResult.JobDone:
                    break;
                case CompletionResult.Exception:
                    string msg = Helper.GetAllExceptionMessages(e.Exception);
                    Messenger.Default.Send(msg, MessageToken);
                    break;
                case CompletionResult.Cancel:
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
        #endregion

        #region "Public Methods"

        public void SetContract(Contract contract)
        {
            if (contract == null) throw new ArgumentNullException("contract");
            _schedule.Contract = contract;
            RefreshBinding();
        }

        public void SetSigner(Signer signer)
        {
            if (signer == null) throw new ArgumentNullException("signer");
            _schedule.Signer = signer;
            RefreshBinding();
        }

        public void SetDetail(ScheduleDetail detail)
        {
            if (detail == null) throw new ArgumentNullException("detail");
            detail.Schedule = _schedule;
            Details.Add(detail);
            RaisePropertyChanged("Details");
        }

        #endregion

        #region Overrides of Workspace

        public override sealed string DisplayName
        {
            get { return _displayName; }
            protected set
            {
                _displayName = value;
                RaisePropertyChanged("DisplayName");
            }
        }

        public override bool HasChanges
        {
            get
            {
                if (!_isNewSchedule)
                {
                    return _unitOfWork.HasChanges;
                }
                return BegunAddingNew();
            }
        }

        private bool BegunAddingNew()
        {
            return (!string.IsNullOrEmpty(RegistredAt)) ||
                   (!string.IsNullOrEmpty(PropertyNo)) ||
                   (!string.IsNullOrEmpty(SignerId)) ||
                   Details != null && Details.Count != 0;
        }

        public override void Cleanup()
        {
            base.Cleanup();
            _unitOfWork.Dispose();
            Details.CollectionChanged -= OnDetailsChanged;
            //SchedulePrinterHelper.Service.JobCompleted -= OnSchedulePrinterCompleted;
        }

        #endregion

        #region Implementation of IDataErrorInfo

        /// <summary>
        /// Gets the error message for the property with the given name.
        /// </summary>
        /// <returns>
        /// The error message for the property. The default is an empty string ("").
        /// </returns>
        /// <param name="columnName">The name of the property whose error message to get. </param>
        public string this[string columnName]
        {
            get { return _errors.ContainsKey(columnName) ? _errors[columnName] : string.Empty; }
        }

        /// <summary>
        /// Gets an error message indicating what is wrong with this object.
        /// </summary>
        /// <returns>
        /// An error message indicating what is wrong with this object. The default is an empty string ("").
        /// </returns>
        public string Error
        {
            get { return null; }
        }

        private void SetError(string propertyName, string errorMessage)
        {
            _errors[propertyName] = errorMessage;
            RaisePropertyChanged(propertyName);
        }

        private void ClearAllErrors()
        {
            List<string> proeprties = _errors.Select(error => error.Key).ToList();
            _errors.Clear();
            foreach (string property in proeprties)
            {
                RaisePropertyChanged(property);
            }
        }

        #endregion

        #region "Search Schedule"

        public ICommand SearchCommand
        {
            get { return _searchCommand ?? (_searchCommand = new RelayCommand(Search)); }
        }

        public SearchSchedule SearchSchedule
        {
            get { return _searchSchedule; }
            private set
            {
                _searchSchedule = value;
                RaisePropertyChanged("SearchSchedule");
            }
        }

        [Bindable(true)]
        public string SearchCriteria
        {
            get { return _searchCriteria; }
            set
            {
                _searchCriteria = value;
                RaisePropertyChanged("SearchCriteria");
            }
        }

        [Bindable(true)]
        public Schedule ScheduleToOpen
        {
            get { return _searchResultSchedule; }
            set
            {
                _schedule = value;
                _searchResultSchedule = value;
                //SyncDetails(_schedule.ScheduleDetails);
                OnStateChanged(ViewModelState.Loaded);
                RefreshBinding();
            }
        }

        public string Temp { get; set; }

        private void Search()
        {
            if (string.IsNullOrEmpty(SearchCriteria)) return;
            List<Schedule> result = _repository.Query(s => s.Contract.Property.PropertyNo == SearchCriteria).ToList();
            if (result.Count() != 0)
            {
                SearchSchedule = new SearchSchedule(result);
                SearchSchedule.ShowResult = true;
            }
        }

        #endregion
    }
}